package neuralNetwork;

public class NeuralNetwork {
	
	private NeuralNetworkLayer sensors; 
	private NeuralNetworkLayer[] hiddenLayers; 
	private NeuralNetworkLayer outputs; 
	
	
	public NeuralNetwork(int[] sizes){
		hiddenLayers = new NeuralNetworkLayer[sizes.length-2]; 
		outputs = new NeuralNetworkLayer(null, sizes[sizes.length-1]); 
		NeuralNetworkLayer inFrontLayer = outputs; 
		for (int i = hiddenLayers.length - 1; i >= 0; i--){
			inFrontLayer = new NeuralNetworkLayer(inFrontLayer, sizes[i+1]);
			hiddenLayers[i] = inFrontLayer; 
		}
		sensors = new NeuralNetworkLayer(inFrontLayer, sizes[0]); 
	}
	
	
	public double[] getWeights(){
		double[] weights = new double[nofWeights()]; 
		int index = 0; 
		index = ArrayOperations.insertData(weights, sensors.getWeights(), index); 
		for (NeuralNetworkLayer layer : hiddenLayers) {
			index = ArrayOperations.insertData(weights, layer.getWeights(), index);
		}
		index = ArrayOperations.insertData(weights, outputs.getWeights(), index);
		return weights; 
	}
	
	public void setWeights(double[] weights){
		int index = 0; 
		int nofWeights = sensors.nofWeights();
		sensors.setWeights(ArrayOperations.selectData(weights, index, nofWeights)); 
		for (NeuralNetworkLayer layer : hiddenLayers) {
			index += nofWeights; 
			nofWeights = layer.nofWeights(); 
			layer.setWeights(ArrayOperations.selectData(weights, index, nofWeights)); 
		}
		index += nofWeights; 
		nofWeights = outputs.nofWeights(); 
		outputs.setWeights(ArrayOperations.selectData(weights, index, nofWeights)); 
	}
	
	public int nofWeights(){
		int nofWeights = 0;
		nofWeights += sensors.nofWeights(); 
		for (NeuralNetworkLayer layer : hiddenLayers) {
			nofWeights += layer.nofWeights(); 
		}
		nofWeights += outputs.nofWeights(); 
		return nofWeights; 
	}
	
	public double[] update(boolean[] sensors){
		
		double[] doubleSensors = new double[sensors.length]; 
		for (int i = 0; i < doubleSensors.length; i++) {
			doubleSensors[i] = sensors[i] ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY; 
		}
		this.sensors.setInput(doubleSensors); 
		
		
		fireUpdate(); 
//		long start = System.currentTimeMillis(); 
//		long end = System.currentTimeMillis(); 
//		System.out.println("Time: " + (end-start));
		return outputs.returnOutput(); 
	}
	
	private void fireUpdate(){
		sensors.fireUpdate(); 
		for (NeuralNetworkLayer layer : hiddenLayers) {
			layer.fireUpdate(); 
		}
		outputs.fireUpdate(); 
	}
	
	public String toString(){
		String theString = ""; 
		theString += "Sensors: " + sensors.toString(); 
		for (int i = 0; i < hiddenLayers.length; i ++){
			theString += "\nHiddenlayer" + Integer.toString(i) + ": " + hiddenLayers[i].toString(); 
		}
		theString += "\nOutputs: " + outputs.toString(); 
		return theString; 
	}
	
	public static class ArrayOperations{
		
		public static double[] selectData(double[] data, int index, int size){
			double[] selection = new double[size]; 
			for (int i = 0; i < size; i++){
				selection[i] = data[index + i]; 
			}
			return selection; 
		}
		
		public static int insertData(double[] data, double[] insert, int index){
			for (int i = 0; i < insert.length; i++){
				data[index++] = insert[i]; 
			}
			return index; 
		}
		
		public static int insertData(boolean[] data, boolean[] insert, int index){
			for (int i = 0; i < insert.length; i++){
				data[index++] = insert[i]; 
			}
			return index; 
		}
	}

}
